﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Text;

namespace RTMP
{
    public enum Protocol : short
    {
        UNDEFINED = -1,
        RTMP = 0,
        RTMPT = 1,
        RTMPS = 2,
        RTMPE = 3,
        RTMPTE = 4,
        RTMFP = 5
    };

    public enum PacketType : byte
    {
        Undefined = 0x00,
        ChunkSize = 0x01,
        Abort = 0x02,
        BytesRead = 0x03,
        Control = 0x04,
        ServerBW = 0x05,
        ClientBW = 0x06,
        Audio = 0x08,
        Video = 0x09,
        Metadata_AMF3 = 0x0F,
        SharedObject_AMF3 = 0x10,
        Invoke_AMF3 = 0x11,
        Metadata = 0x12,
        SharedObject = 0x13,
        Invoke = 0x14,
        FlvTags = 0x16
    };

    public enum HeaderType : byte
    {
        Large = 0,
        Medium = 1,
        Small = 2,
        Minimum = 3
    };
    public class Coding
    {
        #region  Constants


        public static readonly byte[] GenuineFPKey = new byte[62] 
        {
            0x47,0x65,0x6E,0x75,0x69,0x6E,0x65,0x20,0x41,0x64,0x6F,0x62,0x65,0x20,0x46,0x6C,
            0x61,0x73,0x68,0x20,0x50,0x6C,0x61,0x79,0x65,0x72,0x20,0x30,0x30,0x31, // Genuine Adobe Flash Player 001
            
            0xF0,0xEE,
            0xC2,0x4A,0x80,0x68,0xBE,0xE8,0x2E,0x00,0xD0,0xD1,0x02,0x9E,0x7E,0x57,0x6E,0xEC,
            0x5D,0x2D,0x29,0x80,0x6F,0xAB,0x93,0xB8,0xE6,0x36,0xCF,0xEB,0x31,0xAE
        };

        public static readonly byte[] GenuineFMSKey = new byte[68]  
        {
            0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
            0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
            0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001 

            0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, 0x2e, 0x00, 0xd0, 0xd1,
            0x02, 0x9e, 0x7e, 0x57, 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, 0x93, 0xb8, 0xe6, 0x36,
            0xcf, 0xeb, 0x31, 0xae 
        };

        public static readonly uint[][] rtmpe8_keys = new uint[16][] 
        {
	        new uint[4]{0xbff034b2, 0x11d9081f, 0xccdfb795, 0x748de732},
            new uint[4]	{0x086a5eb6, 0x1743090e, 0x6ef05ab8, 0xfe5a39e2},
	        new uint[4]{0x7b10956f, 0x76ce0521, 0x2388a73a, 0x440149a1},
	        new uint[4]{0xa943f317, 0xebf11bb2, 0xa691a5ee, 0x17f36339},
	        new uint[4]{0x7a30e00a, 0xb529e22c, 0xa087aea5, 0xc0cb79ac},
	        new uint[4]{0xbdce0c23, 0x2febdeff, 0x1cfaae16, 0x1123239d},
	        new uint[4]{0x55dd3f7b, 0x77e7e62e, 0x9bb8c499, 0xc9481ee4},
	        new uint[4]{0x407bb6b4, 0x71e89136, 0xa7aebf55, 0xca33b839},
	        new uint[4]{0xfcf6bdc3, 0xb63c3697, 0x7ce4f825, 0x04d959b2},
	        new uint[4]{0x28e091fd, 0x41954c4c, 0x7fb7db00, 0xe3a066f8},
	        new uint[4]{0x57845b76, 0x4f251b03, 0x46d45bcd, 0xa2c30d29},
	        new uint[4]{0x0acceef8, 0xda55b546, 0x03473452, 0x5863713b},
	        new uint[4]{0xb82075dc, 0xa75f1fee, 0xd84268e8, 0xa72a44cc},
	        new uint[4]{0x07cf6e9e, 0xa16d7b25, 0x9fa7ae6c, 0xd92f5629},
	        new uint[4]{0xfeb1eae4, 0x8c8c3ce1, 0x4e0064a7, 0x6a387c2a},
	        new uint[4]{0x893a9427, 0xcc3013a2, 0xf106385b, 0xa829f927}
        };

        public static readonly uint[] packetSize = { 12, 8, 4, 1 };

        public const int RTMP_DEFAULT_CHUNKSIZE = 128;
        public const int SHA256_DIGEST_LENGTH = 32;
        public const int RTMP_LARGE_HEADER_SIZE = 12;
        public const int RTMP_SIG_SIZE = 1536;
        public const int RTMP_CHANNELS = 65600;

        public const int TIMEOUT_RECEIVE = 15000;

        #endregion



        public static void EncodeString(List<byte> output, string strName, string strValue)
        {
            short length = IPAddress.HostToNetworkOrder((short)strName.Length);
            output.AddRange(BitConverter.GetBytes(length));
            output.AddRange(Encoding.ASCII.GetBytes(strName));
            EncodeString(output, strValue);
        }

        public static void EncodeString(List<byte> output, string strValue)
        {
            output.Add(0x02); // type: String
            short length = IPAddress.HostToNetworkOrder((short)strValue.Length);
            output.AddRange(BitConverter.GetBytes(length));
            output.AddRange(Encoding.ASCII.GetBytes(strValue));
        }

        public static void EncodeBoolean(List<byte> output, string strName, bool bVal)
        {
            short length = IPAddress.HostToNetworkOrder((short)strName.Length);
            output.AddRange(BitConverter.GetBytes(length));
            output.AddRange(Encoding.ASCII.GetBytes(strName));
            EncodeBoolean(output, bVal);
        }

        public static void EncodeBoolean(List<byte> output, bool bVal)
        {
            output.Add(0x01); // type: Boolean
            output.Add(bVal ? (byte)0x01 : (byte)0x00);
        }

        public static void EncodeNumber(List<byte> output, string strName, double dVal)
        {
            short length = IPAddress.HostToNetworkOrder((short)strName.Length);
            output.AddRange(BitConverter.GetBytes(length));
            output.AddRange(Encoding.ASCII.GetBytes(strName));
            EncodeNumber(output, dVal);
        }

        public static void EncodeNumber(List<byte> output, double dVal)
        {
            output.Add(0x00); // type: Number
            byte[] bytes = BitConverter.GetBytes(dVal);
            for (int i = bytes.Length - 1; i >= 0; i--) output.Add(bytes[i]); // add in reversed byte order
        }

        public static void EncodeInt16(List<byte> output, short nVal)
        {
            output.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(nVal)));
        }
	
        public static void EncodeInt24(List<byte> output, int nVal)
        {
            byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(nVal));
            for (int i = 1; i < 4; i++) output.Add(bytes[i]);
        }

        /// <summary>
        /// big-endian 32bit integer
        /// </summary>
        /// <param name="output"></param>
        /// <param name="nVal"></param>
        public static void EncodeInt32(List<byte> output, int nVal, uint offset=0 )
        {
            byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(nVal));
            if (offset == 0)
                output.AddRange(bytes);
            else
                for (int i = 0; i < bytes.Length; i++) output[(int)offset + i] = bytes[i];
        }

        /// <summary>
        /// little-endian 32bit integer
        /// TODO: this is wrong on big-endian processors
        /// </summary>
        /// <param name="output"></param>
        /// <param name="nVal"></param>
        public static void EncodeInt32LE(List<byte> output, int nVal)
        {
            output.AddRange(BitConverter.GetBytes(nVal));
        }



        #region Read Functions

        public static string ReadString(byte[] data, int offset)
        {
            string strRes = "";
            ushort length = ReadInt16(data, offset);
            if (length > 0) strRes = Encoding.ASCII.GetString(data, offset + 2, length);
            return strRes;
        }

        public static string ReadLongString(byte[] data, int offset)
        {
            string strRes = "";
            int length = ReadInt32(data, offset);
            if (length > 0) strRes = Encoding.ASCII.GetString(data, offset + 4, length);
            return strRes;
        }

        public static ushort ReadInt16(byte[] data, int offset)
        {
            return (ushort)((data[offset] << 8) | data[offset + 1]);
        }

        public static int ReadInt24(byte[] data, int offset)
        {
            return (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2];
        }
        /// <summary>
        /// big-endian 32bit integer
        /// </summary>
        /// <param name="data"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        /// 
        public static int ReadInt32(byte[] data, int offset)
        {
            int u1=data[offset] << 24;
            int u2=data[offset + 1] << 16;
            int u3=data[offset + 2] << 8;
            int u4=data[offset + 3];
            int u5=data[offset] << 24 | data[offset + 1] << 16;
        	return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
        }
        /// <summary>
        /// little-endian 32bit integer
        /// TODO: this is wrong on big-endian processors
        /// </summary>
        /// <param name="data"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public static int ReadInt32LE(byte[] data, int offset)
        {
            return BitConverter.ToInt32(data, offset);
        }
        public static long getUTime()
        {
        	return (long)(DateTime.Now-new DateTime(1970,1,1)).TotalMilliseconds;
        }
        public static bool ReadBool(byte[] data, int offset)
        {
            return data[offset] == 0x01;
        }

        public static double ReadNumber(byte[] data, int offset)
        {
            byte[] bytes = new byte[8];
            Array.Copy(data, offset, bytes, 0, 8);
            Array.Reverse(bytes); // reversed byte order
            return BitConverter.ToDouble(bytes, 0);
        }

        #endregion


    }
}
